package org.lgiavedoni.android.franq.util.implementation;

import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;

import org.lgiavedoni.android.franq.core.Context;
import org.lgiavedoni.android.franq.core.service.Service;
import org.lgiavedoni.android.franq.service.log.Logger;
import org.lgiavedoni.android.franq.util.JSONUtil;
import org.lgiavedoni.android.franq.util.classU.ClassUtil;
import org.lgiavedoni.android.franq.util.resource.ResourceUtil;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

public class ImplementationUtil {

	private static final Map<String, List<Implementation>> implementations = new HashMap<String, List<Implementation>>();

	public static void addImplementationSource(File file) {
		try {
			if (file.isDirectory()) {
				for (File f : file.listFiles()) {
					if (!f.isDirectory() && f.getName().endsWith(".xml"))
						addImplementationSource(new FileInputStream(ResourceUtil.inst.getResource(f)));
				}
			} else {
				addImplementationSource(new FileInputStream(file));
			}
		} catch (Exception e) {
			Logger.inst.error(e);
		}
	}

	public static void addImplementation(String key, Class clazz) {
		if (implementations.get(key) == null)
			implementations.put(key, new ArrayList<Implementation>());

		implementations.get(key).add(new Implementation(clazz.getCanonicalName()));
	}

	public static void addImplementation(Class clazz) {
		addImplementation(clazz.getCanonicalName(), clazz);
	}

	public static void addImplementationSource(InputStream is) {
		final List<Implementation> tmp = new ArrayList<Implementation>();
		String key = null;
		try {

			DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
			DocumentBuilder db = dbf.newDocumentBuilder();
			Document doc = db.parse(is);
			doc.getDocumentElement().normalize();
			// get the first "real" node
			Node mainNode = null;
			for (int i = 0; i < doc.getChildNodes().getLength(); i++) {
				if (doc.getChildNodes().item(i).getNodeType() == 1) {
					mainNode = doc.getChildNodes().item(i);
					break;
				}
			}
			if (mainNode == null) {
				Logger.inst.error("MainNode Null");
				return;
			}
			key = mainNode.getNodeName();
			NodeList nodeLst = mainNode.getChildNodes();// doc.getElementsByTagName(key);

			for (int s = 0; s < nodeLst.getLength(); s++) {
				if (nodeLst.item(s).getNodeType() == 1) {
					Node node = nodeLst.item(s);
					Implementation imp = new Implementation(node.getAttributes().getNamedItem("class").getNodeValue());
					if (node.getChildNodes() != null && node.getChildNodes().getLength() > 0) {
						for (int i = 0; i < node.getChildNodes().getLength(); i++) {
							if (node.getChildNodes().item(i).getNodeName().equalsIgnoreCase("property")) {
								String att = node.getChildNodes().item(i).getAttributes().getNamedItem("name").getNodeValue();
								String value = JSONUtil.getTextContent(node.getChildNodes().item(i));
								imp.addProperty(att, value);
							}
						}
					}
					// tmp.add(new
					// Implementation(JSONUtil.getTextContent(nodeLst.item(s))));
					tmp.add(imp);
				}
			}

			if (!implementations.containsKey(key)) {
				implementations.put(key, new ArrayList<Implementation>());
			}
			implementations.get(key).addAll(tmp);

		} catch (Exception e) {
			Logger.inst.error(e);
		}
	}

	public static List<Implementation> getImplementations(String key) {
		return implementations.get(key) != null ? implementations.get(key) : new ArrayList<Implementation>();
	}

	public static List<Implementation> getImplementations(Class clazz) {
		List<Implementation> result = new ArrayList<Implementation>();
		try {
			for (String keyValue : implementations.keySet()) {
				for (Implementation implementation : implementations.get(keyValue)) {

					if (implementation != null && implementation.getClazz() != null && implementation.getClazz().getInterfaces() != null) {
						for (Class c : implementation.getClazz().getInterfaces()) {
							if (c.getCanonicalName().equals(clazz.getCanonicalName())) {
								result.add(implementation);
							}
						}

						if (implementation.getClazz().getSuperclass().getCanonicalName().equals(clazz.getCanonicalName())) {
							result.add(implementation);
						}
					}

				}
			}
		} catch (Exception e) {
			Logger.inst.error(e);
		}
		return result;
	}

	public static List<Object> getImplementationsInst(Class clazz) {
		List<Object> result = new ArrayList();
		boolean inst = false;
		for (Implementation imp : getImplementations(clazz)) {
			Method a = null;
			inst = false;

			try {
				for (Field f : imp.getClazz().getDeclaredFields()) {
					if (ClassUtil.isInstanceOf(f.getType(), clazz)) {
						result.add(f.get(null));
						inst = true;
						continue;
					}
				}
				if (!inst) {
					// Create a new instance
					result.add(imp.getClazz().newInstance());
				}

			} catch (Exception e) {
				Logger.inst.error(e);
			}

		}

		return result;
	}

	public static Collection<Object> initialize(List<Implementation> list) {
		List<Object> result = new ArrayList<Object>();
		for (Implementation imp : list) {
			try { // Get the class
				Class clazz = imp.getClazz();
				Object classInstance;
				if (getInstance(clazz) != null) {
					classInstance = getInstance(clazz);
				} else {
					// Object classInstance = clazz.newInstance();
					classInstance = Context.getResource(clazz);
					if (classInstance instanceof Service)
						Logger.inst.error("The service: [" + imp.getClazz() + "] don't have the inst attribute! ");
				}
				// Set the properties
				ClassUtil.setProperties(classInstance, imp.getProperties());
				result.add(classInstance);
			} catch (Exception e) {
				Logger.inst.error(e);
			}
		}
		return result;

	}

	private static Object getInstance(Class clazz) {
		for (Field field : clazz.getDeclaredFields()) {
			if (field.getName().equals("inst") && field.getDeclaringClass().equals(clazz)) {
				try {
					return field.get(null);
				} catch (Exception e) {
				}
			}
		}
		return null;
	}

}
